package com.typhoon.spring_jdbctemplate.compare;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;

import com.typhoon.spring_jdbctemplate.util.Md5;

/**
 * 对比
 * 
 * @author Typhoon
 *
 * @param <S>
 * @param <T>
 */
public abstract class AbstractComparator<S, T> implements IComparator<S, T> {
	private Logger logger = LoggerFactory.getLogger(AbstractComparator.class);
	private CompareRule defaultRule;

	public AbstractComparator() {

	}

	public CompareRule getDefaultRule() {
		return this.defaultRule;
	}

	public void setDefaultRule(CompareRule defaultRule) {
		this.defaultRule = defaultRule;
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	public CompareResult<T> compare(CompareContext context) {
		CompareRule rule = (context.getRule() != null) ? context.getRule() : getDefaultRule();

		Assert.notNull(rule, "CompareRule can't be null.");

		Object source = context.getSource();
		Object target = context.getTarget();

		Assert.notNull(source, "Source can't be null.");
		Assert.notNull(target, "Target can't be null.");

		if ((source instanceof Collection) && (target instanceof Collection)) {// 如果都是集合类型,直接比较
			return doCompare((Collection) source, (Collection) target, rule);
		}

		Collection sourceList;
		if ((!(source instanceof Collection)) && (!(target instanceof Collection))) {// 如果都不是集合类型,转换成list比较
			sourceList = new ArrayList(1);// 避免初始化过多内存空间
			sourceList.add(source);

			Collection targetList = new ArrayList();
			targetList.add(target);

			return doCompare(sourceList, targetList, rule);
		}

		if ((!(source instanceof Collection)) && (target instanceof Collection)) {
			sourceList = new ArrayList(1);
			sourceList.add(source);

			return doCompare(sourceList, (Collection) target, rule);
		}

		if ((source instanceof Collection) && (!(target instanceof Collection))) {
			Collection targetList = new ArrayList();
			targetList.add(target);

			return doCompare((Collection) source, targetList, rule);
		}

		throw new IllegalArgumentException(String.format("Not support compare %s vs %s", new Object[] { source, target }));
	}

	public void onUpdate(S source, T target) {
	}

	public void onDelete(T target) {
	}

	/**
	 * 真正的执行对比操作
	 * 
	 * @author Typhoon
	 * @date 2017-09-17 10:25 Sunday
	 * @since V2.0
	 * @param source
	 * @param target
	 * @param rule
	 * @return
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	protected CompareResult<T> doCompare(Collection<S> source, Collection<T> target, CompareRule rule) {
		this.logger.info("comparing...");
		long start = System.currentTimeMillis();
		CompareResult<T> result = new CompareResult<>();
		T tmpTarget;
		Iterator i$;
		if (null != source && !source.isEmpty()) {// 先遍历源数据,可以对比出需要增加和更新的内容
			tmpTarget = null;
			for (i$ = source.iterator(); i$.hasNext();) {
				S s = (S) i$.next();
				if (isNeedCreate(s, target, rule)) {// ①先检查是否有需要新增的
					tmpTarget = onCreate(s);// 初始化一些属性
					// 将源数据元素加入到新增列表
					result.getNewList().add(tmpTarget);
				} else if ((tmpTarget = getUpdateObject(s, target, rule)) != null) {// ②检查是否需要更新
					// 有相等的元素
					// 获取目标类的hashcode
					int beforeUpdateHash = HashCodeBuilder.reflectionHashCode(tmpTarget, true);

					if (rule.isAutoUpdate()) {// 如果需要自动更新值,直接将源数据值复制到目标类中
						copyProperties(s, tmpTarget);
					}

					onUpdate(s, tmpTarget);// 触发更新的时候做额外一些业务,钩子方法

					// 获取赋值后的目标数据hashcode,其实可以理解为源数据hashcode
					int afterUpdateHash = HashCodeBuilder.reflectionHashCode(tmpTarget, true);

					if (beforeUpdateHash != afterUpdateHash) {// 如果不一致,就放入需要更新的列表中
						result.getUpdateList().add(tmpTarget);
					}
				}
			}
		}
		// Iterator i$;
		if ((target != null) && (target.size() > 0)) {
			for (i$ = target.iterator(); i$.hasNext();) {
				T t = (T) i$.next();
				// 将目标列表元素和源数据列表对比,如果源数据中没有,说明该元素需要删除
				if (isNeedDelete(source, t, rule)) {
					onDelete(t);
					result.getDeleteList().add(t);
				}
			}
		}
		long end = System.currentTimeMillis();
		this.logger.info("complete..time-consuming : " + (end - start) + "ms");
		return result;
	}

	/**
	 * 将源数据内容复制到目标数据中
	 * 
	 * @param source
	 * @param target
	 */
	protected void copyProperties(S source, T target) {
		try {
			PropertyUtils.copyProperties(target, source);
		} catch (Exception e) {
			this.logger.error("Error occur:", e);
		}
	}

	/**
	 * 根据对比规则和数据返回唯一结果
	 * 
	 * @author Typhoon
	 * @date 2017-09-17 10:41 Sunday
	 * @since V2.0
	 * @param obj
	 * @param fields
	 * @param joinChar
	 * @param hash
	 * @return
	 */
	private String getCompareValue(Object obj, String[] fields, String joinChar, boolean hash) {
		Assert.notNull(obj, "Object can't be null.");
		Assert.notEmpty(fields, "Compare fields can't be empty.");

		StringBuffer sb = new StringBuffer();
		try {
			// 用标记把value连起来
			Object tmp = null;
			for (String field : fields) {// 将对比规则中需要比较的属性和对应的值使用连接符号拼接起来,类似id_1
				if ((joinChar != null) && (sb.length() > 0)) {
					sb.append(joinChar);
				}
				tmp = PropertyUtils.getProperty(obj, field);
				sb.append((tmp == null) ? "" : tmp.toString());
			}
		} catch (Exception e) {
			this.logger.error("Error occur:", e);
		}
		String value = sb.toString();
		return ((hash) ? Md5.getMD5ofStr(value) : value);// 将拼接结果转换成字符串后返回(唯一字符串)
	}

	/**
	 * 判断源数据和目标数据是否相等
	 * <p>
	 * 比较规则自定义
	 * </p>
	 * 
	 * @author Typhoon
	 * @date 2017-09-17 10:34 Sunday
	 * @since V2.0
	 * @param source
	 * @param target
	 * @param rule
	 * @return
	 */
	private boolean equals(Object source, Object target, CompareRule rule) {
		Assert.notNull(rule, "CompareRule can't be null.");
		// 根据属性比较两个对象是否相等
		String sValue = getCompareValue(source, rule.getSourceAttrs(), rule.getJoinChar(), rule.isHash());

		String tValue = getCompareValue(target, rule.getTargetAttrs(), rule.getJoinChar(), rule.isHash());

		return sValue.equals(tValue);
	}

	/**
	 * 由源数据单元素和目标列表对比,检查是否需要新增
	 * 
	 * @author Typhoon
	 * @date 2017-09-17 10:29 Sunday
	 * @since V2.0
	 * @param s 源数据单个元素
	 * @param target 目标列表
	 * @param rule 比较规则
	 * @return
	 */
	private boolean isNeedCreate(S s, Collection<T> target, CompareRule rule) {
		Iterator<T> i$;
		if (null == target || target.isEmpty()) {// 目标没有数据,直接判定需要新增
			return true;
		}
		for (i$ = target.iterator(); i$.hasNext();) {
			Object t = i$.next();
			if (equals(s, t, rule)) {// 如果找到目标列表中与源数据匹配的数据,说明改数据存在,不需要新增
				return false;
			}
		}
		return true;
	}

	/**
	 * 从源数据获取需要更新的元素
	 * 
	 * @param s
	 * @param target
	 * @param rule
	 * @return
	 */
	private T getUpdateObject(S s, Collection<T> target, CompareRule rule) {
		Iterator<T> i$;
		if (null == target || target.isEmpty()) {
			return null;
		}
		for (i$ = target.iterator(); i$.hasNext();) {
			T t = i$.next();
			if (equals(s, t, rule)) {// 如果有判定属性相等的内容,返回目标列表中的该元素
				return t;
			}
		}
		return null;
	}

	/**
	 * 检查是否需要删除
	 * 
	 * @author Typhoon
	 * @date 2017-09-17 11:02 Sunday
	 * @since V2.0
	 * @param source
	 * @param t
	 * @param rule
	 * @return
	 */
	private boolean isNeedDelete(Collection<S> source, T t, CompareRule rule) {
		Iterator<S> i$;
		if (null == source || source.isEmpty()) {
			return true;
		}
		for (i$ = source.iterator(); i$.hasNext();) {
			Object s = i$.next();
			if (equals(s, t, rule)) {
				return false;
			}
		}
		return true;
	}
}